/**
 * Copyright(c) kyle.
 */
package org.example.plugin_annotation_processor;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementScanner8;
import javax.tools.Diagnostic;
import java.util.EnumSet;

import static javax.lang.model.element.ElementKind.FIELD;
import static javax.lang.model.element.Modifier.*;

/**
 * 命名检查器
 * <p>
 * 如果程序命名不合规范，将会输出一个编译器的WARNING信息
 *
 * @author kyle
 * @version 1.00.00
 * @since [2020-12-05 23:55]
 * @updateUser [Kyle]
 * @updateRemark [说明本次修改内容]
 * @date [2020-12-05 23:55]
 */
public class NameChecker {
    private final Messager messager;

    private final NameCheckScanner nameCheckScanner = new NameCheckScanner();

    public NameChecker(ProcessingEnvironment processingEnv) {
        this.messager = processingEnv.getMessager();
    }

    /**
     * 对Jva程序命名进行检查，根据《Java语言规范》的第三版第6.8节的要求，Java程序明明应当符合下列格式：
     * <ul>
     *     <li>类或接口：符合驼峰式命名法，首字母小写</li>
     *     <li>方法：符合驼峰式命名法，首字母小写</li>
     *     <li>字段：
     *         <ul>
     *               <li>类、实例变量：符合驼峰式命名法，首字母小写</li>
     *               <li>常量：要求全部由大写字母或下划线构成，并且第一个字符不能是下划线</li>
     *         </ul>
     *     </li>
     * </ul>
     *
     * @param element
     */
    public void checkNames(Element element) {
        nameCheckScanner.scan(element);
    }

    /**
     * 名称检查其实现类，继承了JDK8的
     * 将会以Visitor模式访问抽象语法树中的新元素
     */
    private class NameCheckScanner extends ElementScanner8<Void, Void> {
        @Override
        public Void visitType(TypeElement e, Void aVoid) {
            scan(e.getTypeParameters(), aVoid);
            checkCamelCase(e, true);
            return super.visitType(e, aVoid);
        }

        /**
         * 检查传入的Element是否符合驼峰式命名法，如果不符合，则输出警告信息
         *
         * @param e
         * @param initialCaps
         */
        private void checkCamelCase(Element e, boolean initialCaps) {
            String name = e.getSimpleName().toString();
            boolean previousUpper = false;
            boolean conventioal = true;
            int firstCodePoint = name.codePointAt(0);
            if (Character.isUpperCase(firstCodePoint)) {
                previousUpper = true;
                if (!initialCaps) {
                    messager.printMessage(Diagnostic.Kind.WARNING, "名称 “" + name + "” 应当以小写字母开头", e);
                    return;
                }
            } else if (Character.isLowerCase(firstCodePoint)) {
                if (initialCaps) {
                    messager.printMessage(Diagnostic.Kind.WARNING, "名称 “" + name + "” 应当以大写字母开头", e);
                    return;
                }
            } else {
                conventioal = false;
            }

            if (conventioal) {
                int cp = firstCodePoint;
                for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) {
                    cp = name.codePointAt(i);
                    if (Character.isUpperCase(cp)) {
                        if (previousUpper) {
                            conventioal = false;
                            break;
                        }
                        previousUpper = true;
                    } else {
                        previousUpper = false;
                    }
                }
            }
            if (!conventioal) {
                messager.printMessage(Diagnostic.Kind.WARNING, "名称 “" + name + "” 应当符合驼峰式命名法(Camel Case Names)", e);
            }

        }

        @Override
        public Void visitExecutable(ExecutableElement e, Void aVoid) {
            if (e.getKind() == ElementKind.METHOD) {
                Name name = e.getSimpleName();
                if (name.contentEquals(e.getEnclosingElement().getSimpleName())) {
                    messager.printMessage(Diagnostic.Kind.WARNING, "一个普通方法 “" + name + "” 不应当与类名产生重复，避免与构造函数产生混淆", e);
                }
                checkCamelCase(e, false);
            }
            return super.visitExecutable(e, aVoid);
        }

        @Override
        public Void visitVariable(VariableElement e, Void aVoid) {
            if (e.getKind() == ElementKind.ENUM_CONSTANT ||
                    e.getConstantValue() != null ||
                    heuristicallyConstant(e)
            ) {
                checkAllcaps(e);
            } else {
                checkCamelCase(e, false);
            }
            return super.visitVariable(e, aVoid);
        }

        /**
         * 大写民兵革命检查，要求第一个字母必须是大写的英文字母，其余部分可以是下划线或大写字母
         *
         * @param e
         */
        private void checkAllcaps(VariableElement e) {
            String name = e.getSimpleName().toString();
            boolean converntial = true;
            int firsrCodePoint = name.codePointAt(0);

            if (!Character.isUpperCase(firsrCodePoint)) {
                converntial = false;
            } else {
                boolean previousUnderscore = false;
                int cp = firsrCodePoint;
                for (int i = Character.charCount(cp); i < name.length(); i++) {
                    cp = name.codePointAt(i);
                    if (cp == (int) '_') {
                        if (previousUnderscore) {
                            converntial = false;
                            break;
                        }
                        previousUnderscore = true;
                    } else {
                        previousUnderscore = false;
                        if (Character.isUpperCase(cp) && !Character.isDigit(cp)) {
                            converntial = false;
                            break;
                        }
                    }
                }
            }
            if (!converntial) {
                messager.printMessage(Diagnostic.Kind.WARNING, "常量 “" + name + "” 应当全部以大写字母或下划线命名，并且以字母开头", e);
            }
        }

        /**
         * 判断一个变量是否是常量
         *
         * @param e
         * @return
         */
        private boolean heuristicallyConstant(VariableElement e) {
            if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE) {
                return false;
            } else {
                return e.getKind() == FIELD && e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL));
            }
        }
    }
}
